package com.un.ebs.inventory.service.impl;

import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.un.ebs.core.exception.BusinessEntityNotFoundException;
import com.un.ebs.core.exception.BusinessException;
import com.un.ebs.core.exception.BusinessInventoryNotEnoughException;
import com.un.ebs.core.util.DateUtils;
import com.un.ebs.core.util.SecurityUtils;
import com.un.ebs.core.util.StringUtils;
import com.un.ebs.finance.service.FinanceSyncService;
import com.un.ebs.inventory.InventoryConfigureCode;
import com.un.ebs.inventory.domain.*;
import com.un.ebs.inventory.dto.JxcVo;
import com.un.ebs.inventory.enums.InventoryTypeEnum;
import com.un.ebs.inventory.mapper.*;
import com.un.ebs.inventory.service.InventoryService;
import com.un.ebs.product.domain.ProductSku;
import com.un.ebs.product.service.ProductSkuService;
import com.un.ebs.sys.service.ConfigureService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.sql.SQLException;
import java.time.LocalDate;
import java.util.*;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author liubo
 * @since 2020-05-23
 */
@Service
public class InventoryServiceImpl extends ServiceImpl<InventoryMapper, Inventory> implements InventoryService {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Resource
    private InventoryLogMapper inventoryLogMapper;

    @Resource
    private InventoryAgeMapper inventoryAgeMapper;

    @Resource
    private InventoryLogExtMapper inventoryLogExtMapper;

    @Resource
    private InventoryExtMapper inventoryExtMapper;

    @Resource
    private InventoryMapper inventoryMapper;

    @Resource
    private InventoryNoteItemExtMapper inventoryNoteItemExtMapper;

    @Resource
    private InventoryNoteMapper inventoryNoteMapper;

    @Resource
    private InventoryAccountMapper inventoryAccountMapper;

    @Resource
    private AccountMonthMapper accountMonthMapper;

    @Resource
    private ProductSkuService skuService;

    @Resource
    private InventoryJxcByWarehouse inventoryJxcByWarehouse;

    @Resource
    private InventoryJxcBySku inventoryJxcBySku;

    @Resource
    private InventoryJxcMapper inventoryJxcMapper;

    @Resource
    private ConfigureService configureService;

    @Resource
    private FinanceSyncService financeSyncService;

    private final Map<String, InventoryNumber> mapInventory = new HashMap<String, InventoryNumber>();

    @Override
    public void plus(String itemId, String warehouseId, String skuId, Double qty, Double price, boolean releaseLock) {
        InventoryNoteItemExt item = queryItemById(itemId);
        if (item == null) {
            throw new BusinessEntityNotFoundException(itemId, "InventoryNoteItem");
        }

        Inventory inventory = queryById(warehouseId, skuId);
        if (inventory == null) {
            inventory = new Inventory();
            inventory.setQty(qty);
            inventory.setSkuId(skuId);
            inventory.setWarehouseId(warehouseId);
            inventory.insert();
        } else {
            int ret = 0;
            if (releaseLock) {
                ret = baseMapper.plusLockRelease(warehouseId, skuId, qty, inventory.getQty(), inventory.getLockByInQty());
            } else {
                ret = baseMapper.plus(warehouseId, skuId, qty, inventory.getQty());
            }
            if (ret != 1) {
                throw new BusinessException("增加库存异常。");
            }
        }
        updateAccount(skuId, item.getNoteType(), new Date(), qty, new BigDecimal(qty * new BigDecimal(price).setScale(4, RoundingMode.UP).doubleValue()).setScale(4, RoundingMode.UP).doubleValue());
    }

    @Override
    public void reduce(String itemId, String warehouseId, String skuId, Double qty, Double price, boolean releaseLock) {
        InventoryNoteItemExt item = queryItemById(itemId);
        if (item == null) {
            throw new BusinessEntityNotFoundException(itemId, "InventoryNoteItem");
        }

        Inventory inventory = queryById(warehouseId, skuId);
        if (inventory == null) {
            String msg = String.format("扣减库存时，没有找到库存记录：warehouse_id : {%s} ,  sku_id : {%s}", warehouseId, skuId);
            logger.warn(msg);
            throw new BusinessException(msg);
        } else {
            if (inventory.getQty() - qty < 0) {
                throw new BusinessInventoryNotEnoughException(String.format("%s【%s】中在扣减库存时，%s 库存不足。", item.getNoteTypeName(), item.getCode(), item.getSkuCode()));
            }

            int ret = 0;
            if (releaseLock) {
                ret = baseMapper.reduceLockRelease(warehouseId, skuId, qty, inventory.getQty(), inventory.getLockByOutQty());
            } else {
                ret = baseMapper.reduce(warehouseId, skuId, qty, inventory.getQty());
            }
            if (ret != 1) {
                String msg = String.format("库存扣减时，update 的返回值不为 1 : warehouse_id : %s , sku_id : %s", warehouseId, skuId);
                throw new BusinessException(msg);
            }
            updateAccount(skuId, item.getNoteType(), new Date(), qty, new BigDecimal(qty * new BigDecimal(price).setScale(4, RoundingMode.UP).doubleValue()).setScale(4, RoundingMode.UP).doubleValue());
        }
    }

    @Override
    public void move(String itemId, String fromWarehouseId, String toWarehouseId, String skuId, Double qty) {
//        reduce(itemId, fromWarehouseId, skuId, qty, 0d, true);
//        plus(itemId, toWarehouseId, skuId, qty, 0d, true);
//        LambdaQueryWrapper<InventoryLog> lq = Wrappers.lambdaQuery();
//        lq.eq(InventoryLog::getNoteItemId, itemId);
//        List<InventoryLog> listLog = inventoryLogMapper.selectList(lq);
//        if (listLog.size() == 2 && listLog.stream().mapToDouble(s -> s.getCurrLockByOutQty() + s.getCurrLockByInQty()).sum() == 0) {
//            reduce(itemId, fromWarehouseId, skuId, qty, null, true);
//            plus(itemId, toWarehouseId, skuId, qty, null, true);
//        } else {
//            String msg = String.format("库存移动时，原有库存日志异常， 日志笔数：%d, 合计数量: %s ", listLog.size(), listLog.stream().mapToDouble(s -> s.getCurrLockByOutQty() + s.getCurrLockByInQty()).sum());
//            logger.warn(msg);
//            throw new BusinessException(msg);
//        }
    }

    @Override
    public void lockByPlus(String itemId, String warehouseId, String skuId, Double qty) {
        Inventory inventory = queryById(warehouseId, skuId);
        if (inventory == null) {
            inventory = new Inventory();
            inventory.setLockByInQty(qty);
            inventory.setSkuId(skuId);
            inventory.setWarehouseId(warehouseId);
            inventory.insert();
        } else {
            int cnt = baseMapper.plusLock(warehouseId, skuId, qty, inventory.getLockByInQty());
            if (cnt != 1) {
                String msg = String.format("入库锁定时，update 的返回值不为 1 : warehouse_id : %s , sku_id : %s", warehouseId, skuId);
                throw new BusinessException(msg);
            }
        }
    }

    @Override
    public void lockByReduce(String itemId, String warehouseId, String skuId, Double qty) {
        Inventory inventory = queryById(warehouseId, skuId);
        if (inventory == null && qty > 0) {
            ProductSku sku = skuService.queryById(skuId);
            throw new BusinessException(sku.getSkuCode() + " 没有库存。");
        } else {
            int cnt = baseMapper.reduceLock(warehouseId, skuId, qty, inventory == null ? 0 : inventory.getLockByOutQty());
            if (cnt != 1) {
                String msg = String.format("出库锁定时，update 的返回值不为 1 : warehouse_id : %s , sku_id : %s", warehouseId, skuId);
                if (qty < 0) {
                    Inventory obj = new Inventory();
                    obj.setSkuId(skuId);
                    obj.setWarehouseId(warehouseId);
                    obj.setQty(qty);
                    obj.setLockByInQty(0d);
                    obj.setLockByOutQty(0d);
                    obj.setCreateTime(new Date());
                    baseMapper.insert(obj);
                    logger.warn(msg);
                } else {
                    throw new BusinessException(msg);
                }
            }
        }
    }

    @Override
    public void lockByMove(String itemId, String fromWarehouseId, String toWarehouseId, String skuId, Double qty) {
        lockByReduce(itemId, fromWarehouseId, skuId, qty);
        lockByPlus(itemId, toWarehouseId, skuId, qty);
    }

    @Override
    public void lockReleaseByMove(String itemId, String fromWarehouseId, String toWarehouseId, String skuId, Double qty) {
        queryCostPrice(skuId, LocalDate.now().getYear(), LocalDate.now().getMonthValue());
        reduce(itemId, fromWarehouseId, skuId, qty, 0d, true);
        plus(itemId, toWarehouseId, skuId, qty, 0d, true);
    }

    @Transactional
    @Override
    public void reset(String skuId) {

        resetInventory(skuId);
        List<InventoryNoteItemExt> listReduces = new ArrayList<>();
        mapInventory.remove(skuId);

        List<InventoryNoteItemExt> items = queryItem(skuId);
        for (InventoryNoteItemExt item : items) {

            if (item.getInventoryType() == InventoryTypeEnum.IN) {
                lockByPlus(item.getId(), item.getWarehouseId(), item.getSkuId(), item.getQty());
                if (item.getAuditStatus()) {
                    plus(item.getId(), item.getWarehouseId(), item.getSkuId(), item.getQty(), item.getPrice(), true);
                    updateCacheInventory(item);

                }
            } else if (item.getInventoryType() == InventoryTypeEnum.OUT) {
                lockByReduce(item.getId(), item.getWarehouseId(), item.getSkuId(), item.getQty());
                if (item.getAuditStatus()) {
                    try {
                        double costPrice = updateCacheInventory(item);
                        reduce(item.getId(), item.getWarehouseId(), item.getSkuId(), item.getQty(), costPrice, true);
                        //inventoryNoteMapper.updateCostPrice(item.getId(), costPrice);
                    } catch (BusinessInventoryNotEnoughException e) {
                        //库存不足的，先记下，所有业务跑完后，再处理
                        listReduces.add(item);
                    }
                }
            } else {
                System.out.println(String.format("无效的库存业务类别 %s : %s", item.getInventoryType().getDesc(), item.getCode()));
            }
        }

        //System.out.println("库存等处理 ： " + listReduces.size());
        for (InventoryNoteItemExt item : listReduces) {
            if (item.getInventoryType() != InventoryTypeEnum.OUT) {
                continue;
            }
            //之前库存不足的，继续处理，如果仍然不足，则直接给出异常，这种情况不应该发生。
            System.out.println("之前库存不足的，继续处理，如果仍然不足，则直接给出异常，这种情况不应该发生。");
            double costPrice = updateCacheInventory(item);

            reduce(item.getId(), item.getWarehouseId(), item.getSkuId(), item.getQty(), costPrice, true);
            //inventoryNoteMapper.updateCostPrice(item.getId(), costPrice);


        }
    }

    private Double updateCacheInventory(InventoryNoteItemExt item) {
        if (!item.getAuditStatus()) {
            return null;
        }

        Double retValue = null;
        //System.out.println();

        if (item.getInventoryType() == InventoryTypeEnum.IN) {
            //System.out.println("入库：");
            //System.out.println("---------------------------------------------------");
            //System.out.println(String.format("金额：%s，数量：%s，价格：%s", item.getPrice() * item.getQty(), item.getQty(), retValue));
            //BigDecimal money = new BigDecimal(item.getQty() * item.getPrice()).setScale(6, RoundingMode.HALF_UP);
            BigDecimal money = new BigDecimal(item.getQty()).multiply(new BigDecimal(item.getPrice()).setScale(6, RoundingMode.HALF_UP));

            if (mapInventory.get(item.getSkuId()) == null) {
                mapInventory.put(item.getSkuId(), InventoryNumber.value(item.getQty(), money.doubleValue()));
            } else {
                mapInventory.replace(item.getSkuId(),
                        mapInventory.get(item.getSkuId()),
                        InventoryNumber.value(
                                mapInventory.get(item.getSkuId()).getQty() + item.getQty(),
                                new BigDecimal(mapInventory.get(item.getSkuId()).getMoney()).add(money).doubleValue()));
            }
            retValue = item.getPrice();
            //System.out.println(String.format("金额：%s，数量：%s，价格：%s", mapInventory.get(item.getSkuId()).money, mapInventory.get(item.getSkuId()).getQty(), retValue));
        }

        if (item.getInventoryType() == InventoryTypeEnum.OUT) {
            if (mapInventory.get(item.getSkuId()) == null) {
                System.out.println("出库：");
                System.out.println("---------------------------------------------------");
                System.out.println("没有库存可以扣减。" + item.getSkuCode() + " :::: " + item.getSkuId());
                return 0d;
            } else {
                //System.out.println(String.format("金额：%s，数量：%s，价格：%s", mapInventory.get(item.getSkuId()).money, mapInventory.get(item.getSkuId()).getQty(), retValue));

                if (mapInventory.get(item.getSkuId()).money == 0 || mapInventory.get(item.getSkuId()).getQty() == 0) {
                    //System.out.println(String.format("金额：%s，数量：%s",mapInventory.get(item.getSkuId()).money,mapInventory.get(item.getSkuId()).getQty()));
                    retValue = 0d;
                    mapInventory.replace(item.getSkuId(), mapInventory.get(item.getSkuId()),
                            InventoryNumber.value(mapInventory.get(item.getSkuId()).getQty() - item.getQty(),
                                    mapInventory.get(item.getSkuId()).getMoney()));
                } else {
                    retValue = new BigDecimal(mapInventory.get(item.getSkuId()).money).divide(new BigDecimal(mapInventory.get(item.getSkuId()).getQty()), 6, RoundingMode.HALF_UP).doubleValue();
                    mapInventory.replace(item.getSkuId(), mapInventory.get(item.getSkuId()),
                            InventoryNumber.value(mapInventory.get(item.getSkuId()).getQty() - item.getQty(),
                                    new BigDecimal(mapInventory.get(item.getSkuId()).getMoney()).setScale(6, RoundingMode.HALF_UP).doubleValue()
                                            -
                                            new BigDecimal(item.getQty()).multiply(new BigDecimal(retValue)).setScale(6, RoundingMode.HALF_UP).doubleValue()));
                }
            }
            //System.out.println(String.format("金额：%s，数量：%s，价格：%s", mapInventory.get(item.getSkuId()).money, mapInventory.get(item.getSkuId()).getQty(), retValue));
        }
        //updateAccount(item.getSkuId(), item.getNoteType(), item.getAuditTime(), item.getQty(), new BigDecimal(item.getQty() * retValue).setScale(4, RoundingMode.HALF_UP).doubleValue());
        return new BigDecimal(retValue).setScale(6, RoundingMode.HALF_UP).doubleValue();
    }

    @Override
    public void updateAccount(int year, int month, boolean isRecountCostPrice) {
        String accountMonth = String.format("%d%02d", year, month);
        AccountMonth account = accountMonthMapper.selectByYearMonth(year, month);
        if (account == null) {
            throw new BusinessEntityNotFoundException(accountMonth, "会计期间");
        }
        if (account.getCloseStatus()) {
            throw new BusinessException("指定的会计期间已关账。");
        }

        int index = 1;
        List<InventoryNoteItemExt> list = queryItem(accountMonth, 200, index).getRecords();
        while (list.size() > 0) {
            System.out.println("index : " + index);
            for (InventoryNoteItemExt item : list) {
                if (item.getInventoryType() == InventoryTypeEnum.OUT) {

                    double costPrice = queryCostPrice(item.getSkuId(), year, month);

                    if (isRecountCostPrice) {
                        inventoryNoteMapper.updateCostPrice(item.getId(), costPrice);
                    }
                    updateAccount(item.getSkuId(), item.getNoteType(), item.getAuditTime(), item.getQty(), new BigDecimal(item.getQty()).multiply(new BigDecimal(costPrice)).setScale(6, RoundingMode.HALF_UP).doubleValue());

                } else {

                    BigDecimal money = new BigDecimal(item.getQty()).multiply(new BigDecimal(item.getPrice())).setScale(6, RoundingMode.HALF_UP);
                    updateAccount(item.getSkuId(), item.getNoteType(), item.getAuditTime(), item.getQty(), money.doubleValue());
                }
            }
            index += 1;
            list = queryItem(accountMonth, 200, index).getRecords();
        }
    }

    @Override
    public void updateAccount(int year, int month) throws SQLException {
        AccountMonth accountMonth = accountMonthMapper.selectByYearMonth(year, month);
        if (accountMonth == null) {
            throw new BusinessException(String.format("会计期间【%s年%s月】没有找到。", year, month));
        }
        if (accountMonth.getCloseStatus()) {
            throw new BusinessException(String.format("此会计期间【%s年%s月】已关账。", year, month));
        }

        accountMonthMapper.update(year, month, SecurityUtils.getTenantId());

    }


    @Override
    public void closeAccount(int year, int month) {
        AccountMonth accountMonth = accountMonthMapper.selectByYearMonth(year, month);
        if (accountMonth == null) {
            throw new BusinessException(String.format("会计期间【%s年%s月】没有找到。", year, month));
        }
        if (accountMonth.getCloseStatus()) {
            throw new BusinessException(String.format("此会计期间【%s年%s月】已关账。", year, month));
        }
        accountMonthMapper.close(year, month, SecurityUtils.getUserContent().getId(), SecurityUtils.getTenantId());
    }

    @Override
    public void closeCancelAccount(int year, int month) {
        AccountMonth accountMonth = accountMonthMapper.selectByYearMonth(year, month);
        if (accountMonth == null) {
            throw new BusinessException(String.format("会计期间【%s年%s月】没有找到。", year, month));
        }
        if (!accountMonth.getCloseStatus()) {
            throw new BusinessException(String.format("此会计期间【%s年%s月】未关账。", year, month));
        }

        accountMonthMapper.cancelClose(year, month, SecurityUtils.getTenantId());
    }

    @Override
    public AccountMonth createAccountMonth(int year, int month) {
        AccountMonth accountMonth = queryAccountMonth(DateUtils.parseDate(year, month, 1));
        if (accountMonth == null) {
            accountMonth = new AccountMonth();
            accountMonth.setAccountYear(year);
            accountMonth.setAccountMonth(month);
            accountMonth.setStartDate(DateUtils.parseDate(year, month, 1));
            accountMonth.setEndDate(DateUtils.getLastDayOfMonth(year, month));
            accountMonth.setSyncStatus(false);
            accountMonth.setCloseStatus(false);
            accountMonth.insert();
        }
        return accountMonth;
    }

    @Override
    public IPage<AccountMonth> queryAccountMonth(int pageSize, int pageIndex) {
        Page<AccountMonth> page = new Page<>(pageIndex, pageSize);
        LambdaQueryWrapper<AccountMonth> lq = Wrappers.lambdaQuery();
        lq.orderByDesc(AccountMonth::getStartDate);
        return accountMonthMapper.selectPage(page, lq);
    }

    @Override
    public AccountMonth queryAccountMonth(Date date) {
        LambdaQueryWrapper<AccountMonth> lq = Wrappers.lambdaQuery();
        lq.eq(AccountMonth::getAccountYear, DateUtils.getYear(date));
        lq.eq(AccountMonth::getAccountMonth, DateUtils.getMonth(date));
        return accountMonthMapper.selectOne(lq);
    }

    @Override
    public AccountMonth queryAccountMonthById(String accountId) {
        return accountMonthMapper.selectById(accountId);
    }

    @Override
    public IPage<InventoryAge> queryInventoryAge(int pageSize, int pageIndex) {
        Page<InventoryAge> page = new Page<>(pageIndex, pageSize);
        LambdaQueryWrapper<InventoryAge> lq = Wrappers.lambdaQuery();
        lq.eq(InventoryAge::getTenantId, SecurityUtils.getTenantId());
        lq.orderByAsc(InventoryAge::getClassName);
        return inventoryAgeMapper.selectPage(page, lq);
    }

    @Override
    public double queryCostPrice(String skuId) {
        ProductSku sku = skuService.queryById(skuId);
        if (sku == null) {
            throw new BusinessEntityNotFoundException(skuId, "SKU");
        }

        Optional<JxcVo> obj = queryCurrentJxc(sku.getSkuCode(), 999999, 1).getRecords().stream().findFirst();
        if (obj.isPresent()) {
            return obj.get().getEndPrice();
        } else {
            return 0;
        }
    }

    @Override
    public IPage<JxcVo> queryCurrentJxc(String skuCode, int pageSize, int pageIndex) {
        Page<JxcVo> page = new Page<>(pageIndex, pageSize);
        List<JxcVo> list = baseMapper.queryJxc(skuCode, pageSize, pageIndex, SecurityUtils.getTenantId());
        page.setRecords(list);
        page.setTotal(list.stream().count() > 0 ? list.stream().findFirst().get().getMaxNum() : 0);
        page.setPages((int) page.getTotal() / pageSize);
        return page;
    }

    @Override
    public IPage<InventoryJxcByWarehouse> queryJxcByWarehouse(int year, int month, int pageSize, int pageIndex) {
        IPage<InventoryJxcByWarehouse> page = new Page(pageIndex, pageSize);
        LambdaQueryWrapper<InventoryJxcByWarehouse> lq = Wrappers.lambdaQuery();
        lq.eq(InventoryJxcByWarehouse::getAccountYear, year);
        lq.eq(InventoryJxcByWarehouse::getAccountMonth, month);
        return inventoryJxcByWarehouse.selectPage(page, lq);

    }

    @Override
    public IPage<InventoryJxcBySku> queryJxcBySku(String skuCode, String warehouseId, int year, int month, int pageSize, int pageIndex) {
        IPage<InventoryJxcBySku> page = new Page<>(pageIndex, pageSize);
        LambdaQueryWrapper<InventoryJxcBySku> lq = Wrappers.lambdaQuery();
        if (!StringUtils.isNullOrEmpty(skuCode)) {
            lq.eq(InventoryJxcBySku::getSkuCode, skuCode);
        }
        if (!StringUtils.isNullOrEmpty(warehouseId)) {
            lq.eq(InventoryJxcBySku::getWarehouseId, warehouseId);
        }
        lq.eq(InventoryJxcBySku::getAccountYear, year);
        lq.eq(InventoryJxcBySku::getAccountMonth, month);
        return inventoryJxcBySku.selectPage(page, lq);

    }

    private void updateAccount(String skuId, InventoryTypeEnum noteType, Date dt, double qty, double money) {
        InventoryAccount account = inventoryAccountMapper.selectBySku(skuId, DateUtils.getYear(dt), DateUtils.getMonth(dt));
        if (account == null) {
            account = new InventoryAccount();
            account.setSkuId(skuId);
            account.setAccountYear(DateUtils.getYear(dt));
            account.setAccountMonth(DateUtils.getMonth(dt));
            account.setStartMoney(0d);
            account.setStartQty(0d);
            account.setInMoney(0d);
            account.setInQty(0d);
            account.setOutMoney(0d);
            account.setOutQty(0d);
        }

        switch (noteType) {
            case ACCOUNT_INIT:
                account.setStartQty(new BigDecimal(account.getStartQty()).add(new BigDecimal(qty)).setScale(4, RoundingMode.HALF_UP).doubleValue());
                account.setStartMoney(new BigDecimal(account.getStartMoney()).add(new BigDecimal(money)).setScale(4, RoundingMode.HALF_UP).doubleValue());
                break;
            case PURCHASE_IN:
            case PRODUCTION_IN:
            case MOVE_IN:
            case CHECK_IN:
            case IN:
                account.setInQty(new BigDecimal(account.getInQty()).add(new BigDecimal(qty)).setScale(4, RoundingMode.HALF_UP).doubleValue());
                account.setInMoney(new BigDecimal(account.getInMoney()).add(new BigDecimal(money)).setScale(4, RoundingMode.HALF_UP).doubleValue());
                break;
            case SALE_OUT:
            case PRODUCTION_BOM_USER:
            case PRODUCTION_USER:
            case REPAIR_USER:
            case TECH_OUT:
            case MOVE_OUT:
            case CHECK_OUT:
            case OUT:
                account.setOutQty(new BigDecimal(account.getOutQty()).add(new BigDecimal(qty)).setScale(4, RoundingMode.HALF_UP).doubleValue());
                account.setOutMoney(new BigDecimal(account.getOutMoney()).add(new BigDecimal(money)).setScale(4, RoundingMode.HALF_UP).doubleValue());
                break;
//            case MOVE:
//                account.setOutQty(new BigDecimal(account.getOutQty()).subtract(new BigDecimal(qty)).doubleValue());
//                account.setOutMoney(new BigDecimal(account.getOutMoney()).subtract(new BigDecimal(money)).doubleValue());
//                account.setInQty(new BigDecimal(account.getInQty()).add(new BigDecimal(qty)).doubleValue());
//                account.setInMoney(new BigDecimal(account.getInMoney()).add(new BigDecimal(money)).doubleValue());
//                break;
            default:
                throw new BusinessException("单据类别未处理。");
        }

        account.insertOrUpdate();

    }

    @Override
    public void updateEndCostMoney(String id, Double adjustMoney, String remark) {
        InventoryJxc obj = inventoryJxcMapper.selectById(id);
        if (obj == null) {
            throw new BusinessEntityNotFoundException(id, "进销存明细");
        }
        AccountMonth month = accountMonthMapper.selectByYearMonth(obj.getAccountYear(), obj.getAccountMonth());
        if (month == null) {
            throw new BusinessException("会计月份不存在。");
        }
        if (month.getCloseStatus()) {
            throw new BusinessException("当前会计月已关账，不能调整。");
        }

        obj.setEndAdjustMoney(adjustMoney);
        obj.setEndAdjustDesc(remark);
        obj.setEndAdjustPersonnelName(SecurityUtils.getUserContent().getPersonnelName());
        obj.setEndAdjustTime(new Date());
        obj.updateById();
    }

    @Override
    public void updateEndCostMoneyClear(String id) {
        updateEndCostMoney(id, 0d, null);
    }

    private void resetInventory(String skuId) {
        LambdaQueryWrapper<Inventory> lq = Wrappers.lambdaQuery();
        lq.eq(Inventory::getSkuId, skuId);
        List<Inventory> inventories = inventoryMapper.selectList(lq);

        for (Inventory item : inventories) {
            LambdaQueryWrapper<InventoryLog> lqLog = Wrappers.lambdaQuery();
            lqLog.eq(InventoryLog::getInventoryId, item.getId());
            inventoryLogMapper.delete(lqLog);
        }
        inventoryMapper.delete(lq);
    }

    @Override
    public Inventory queryById(String id) {
        return getBaseMapper().selectById(id);
    }

    @Override
    public Inventory queryById(String warehouseId, String skuId) {
        LambdaQueryWrapper<Inventory> lq = Wrappers.lambdaQuery();
        lq.eq(Inventory::getWarehouseId, warehouseId);
        lq.eq(Inventory::getSkuId, skuId);
        return baseMapper.selectOne(lq);
    }

    @Override
    public List<InventoryExt> queryBySku(String skuId) {
        LambdaQueryWrapper<InventoryExt> lq = Wrappers.lambdaQuery();
        lq.eq(InventoryExt::getSkuId, skuId);
        return inventoryExtMapper.selectList(lq);
    }

    @Override
    public IPage<InventoryExt> query(String searchText, String classId, String warehouseId, Integer pageSize, Integer pageIndex) {
        IPage<InventoryExt> page = new Page(pageIndex, pageSize);
        LambdaQueryWrapper<InventoryExt> lq = Wrappers.lambdaQuery();
        if (!StringUtils.isNullOrEmpty(searchText)) {
            lq.like(InventoryExt::getSkuCode, searchText).or().like(InventoryExt::getNameAs, searchText);
        }
        if (!StringUtils.isNullOrEmpty(classId)) {
            lq.eq(InventoryExt::getClassId, classId);
        }
        if (!StringUtils.isNullOrEmpty(warehouseId)) {
            lq.eq(InventoryExt::getWarehouseId, warehouseId);
        }
        lq.orderByAsc(InventoryExt::getClassId).orderByAsc(InventoryExt::getSkuCode);
        return inventoryExtMapper.selectPage(page, lq);
    }

    @Override
    public IPage<InventoryLogExt> queryLog(String skuId, String warehouseId, String noteCode, Integer pageSize, Integer pageIndex) {
        IPage<InventoryLogExt> page = new Page<>(pageIndex, pageSize);
        LambdaQueryWrapper<InventoryLogExt> lq = Wrappers.lambdaQuery();
        lq.eq(InventoryLogExt::getSkuId, skuId);
        if (!StringUtils.isNullOrEmpty(warehouseId)) {
            lq.eq(InventoryLogExt::getWarehouseId, warehouseId);
        }
        if (!StringUtils.isNullOrEmpty(noteCode)) {
            lq.eq(InventoryLogExt::getNoteCode, noteCode);
        }
        lq.orderByDesc(InventoryLogExt::getCreateTime);
        return inventoryLogExtMapper.selectPage(page, lq);
    }

    @Override
    public double queryCostPrice(String skuId, int year, int month) {
        InventoryAccount account = inventoryAccountMapper.selectBySku(skuId, year, month);
        if (account == null) {
            throw new BusinessException("SKU " + skuId + " 没有总账数据。");
        }

        // 此处的金额和数量不能做四舍五入，否则计算结果会有差异
        double money = new BigDecimal(account.getStartMoney())
                .add(new BigDecimal(account.getInMoney()))
                .subtract(new BigDecimal(account.getOutMoney())).setScale(6, RoundingMode.HALF_UP).doubleValue();
        double qty = new BigDecimal(account.getStartQty())
                .add(new BigDecimal(account.getInQty()))
                .subtract(new BigDecimal(account.getOutQty())).setScale(6, RoundingMode.HALF_UP).doubleValue();

        try {
            if (money == 0 || qty == 0) {
                return 0.00d;
            } else {
                return new BigDecimal(money)
                        .divide(new BigDecimal(qty), 6, RoundingMode.HALF_UP).doubleValue();
            }
        } catch (NumberFormatException e) {
            System.out.println("money : " + money);
            System.out.println("qty   : " + qty);
            System.out.println(e.getMessage());
        }
        return 0;

    }

    @Transactional
    @Override
    public void syncFinance(String accountId) {
        AccountMonth accountMonth = accountMonthMapper.selectById(accountId);
        if (accountMonth == null) {
            throw new BusinessEntityNotFoundException(accountId, "会计期间");
        }
        if (!accountMonth.getCloseStatus()) {
            throw new BusinessException(String.format("当前会计期间【%s年%s月】没有关账，不能进行此操作。", accountMonth.getAccountYear(), accountMonth.getAccountMonth()));
        }
        String deptCode = configureService.getValueByCode(InventoryConfigureCode.tenant_finance_dept_code, accountMonth.getTenantId());
        if (StringUtils.isNullOrEmpty(deptCode)) {
            throw new BusinessException(String.format("当前租户【%s】没有配置对应的核算部门代码", SecurityUtils.getUserContent().getTenantName()));
        }
        if (accountMonth.getSyncStatus()) {
            throw new BusinessException(String.format("当前会计期间【%s年%s月】账务数据已同步，不能重复操作。", accountMonth.getAccountYear(), accountMonth.getAccountMonth()));
        }
        if (accountMonth.getSyncStartTime() != null && accountMonth.getSyncStatus() == false) {
            throw new BusinessException(String.format("当前会计期间【%s年%s月】账务数据正在同步，不能重复操作。", accountMonth.getAccountYear(), accountMonth.getAccountMonth()));
        }

        accountMonth.setSyncAt(SecurityUtils.getUserContent().getId());
        accountMonth.setSyncAtName(SecurityUtils.getUserContent().getPersonnelName());
        accountMonth.setSyncStartTime(new Date());
        accountMonth.updateById();

        //financeSyncService.sync(accountMonth.getAccountYear(), accountMonth.getAccountMonth(), deptCode);
        FinanceSyncRunnable sync = new FinanceSyncRunnable(accountId, financeSyncService);
        sync.run();
    }

    @Override
    public void syncFinanceComplete(String accountId) {
        accountMonthMapper.updateSyncComplete(accountId);
    }

    private List<InventoryNoteItemExt> queryItem(String skuId) {
        LambdaQueryWrapper<InventoryNoteItemExt> lq = Wrappers.lambdaQuery();
        lq.eq(InventoryNoteItemExt::getSkuId, skuId);
        lq.eq(InventoryNoteItemExt::getSubmitStatus, true);
        lq.orderByAsc(InventoryNoteItemExt::getSubmitTime);
        return inventoryNoteItemExtMapper.selectList(lq);
    }

    private IPage<InventoryNoteItemExt> queryItem(String accountMonth, int size, int index) {
        IPage<InventoryNoteItemExt> page = new Page<>(index, size);
        LambdaQueryWrapper<InventoryNoteItemExt> lq = Wrappers.lambdaQuery();
        lq.eq(InventoryNoteItemExt::getAuditStatus, true);
        lq.eq(InventoryNoteItemExt::getSubmitStatus, true);
        lq.eq(InventoryNoteItemExt::getIsDelete, false);
        //lq.eq(InventoryNoteItemExt::getSkuId,"2f90c27c33b3062fe29900bb45ea6bc5");
        lq.apply(" convert(varchar(6),audit_time,112) = '" + accountMonth + "' ");
        lq.orderByAsc(InventoryNoteItemExt::getAuditTime);
        return inventoryNoteItemExtMapper.selectPage(page, lq);
    }

    private InventoryNoteItemExt queryItemById(String itemId) {
        return inventoryNoteItemExtMapper.selectById(itemId);
    }

    public static class InventoryNumber {

        private double qty;

        private double money;

        public InventoryNumber(double qty, double money) {
            this.qty = qty;
            this.money = money;
        }

        public static InventoryNumber value(double qty, double money) {
            return new InventoryNumber(qty, money);
        }

        public double getMoney() {
            return money;
        }

        public void setMoney(double money) {
            this.money = money;
        }

        public double getQty() {
            return qty;
        }

        public void setQty(double qty) {
            this.qty = qty;
        }
    }

    public class FinanceSyncRunnable implements Runnable {

        private final String ACCOUNT_ID;
        private final FinanceSyncService SERVICE;

        public FinanceSyncRunnable(String accountId, FinanceSyncService service) {
            this.ACCOUNT_ID = accountId;
            this.SERVICE = service;
        }

        @Override
        public void run() {
            this.SERVICE.sync(ACCOUNT_ID);
            this.SERVICE.syncComplete(ACCOUNT_ID);
        }
    }

}